See how oath toolkit compares to other vendors in security performance
FYI - also via https://oath-toolkit.codeberg.page/CVE-2026-109117.html
/Simon
Simon Josefsson via OATH Toolkit general discussions <oath-toolkit-help () nongnu org> writes: OATH Toolkit PSKC Signature Verification bypass in libpskc (CVE-2026-109117) ----------------------------------------------------------------------------
Security Vulnerability ----------------------
OATH Toolkit provides a library for handling PSKC data called 'libpskc', and a command-line tool 'pskctool' that uses libpskc.
PSKC is a XML data format defined in RFC 6030 that can be used to transport OATH HOTP/TOTP HMAC-SHA symmetric secret keys. Libpskc relies on the XML Security Library (libxmlsec) for cryptographic signature verification of the PSKC data.
The RFC 6030 specification section 7 available from https://datatracker.ietf.org/doc/html/rfc6030#section-7 is not clear what the purpose and goal of adding a Digital Signature on PSKC data is, nor what the threat model is. It could be argued that rather than authenticating the data for trust purposes, the goal of a PSKC signature could be to provide integrity protection of the content and a data origin identification.
The libxmlsec library before v1.3.3 allowed the XML data to provide a key that will be used to verify the signature, instead of the key provided by the library/application. Version 1.3.3 of libxmlsec changed so that it no longer trusts the key data inside the XML data by default.
PSKC data is rarely encrypted (and OATH Toolkit do not support PSKC encryption), therefore deployments require some outer protection of the PSKC data to protect the OATH HOTP/TOTP secrets from leaking during transport. SSH and HTTPS are typical transport protocols used for this, although offline physical transports (e.g., USB memory devices) is common from production facilities.
However, if an attacker is able to circumvent the outer transport protection and substitute their own PSKC data to a system that relies on PSKC Signature Verification before trusting the incoming PSKC data, the attacker would be able to substitute their own OATH HOTP/TOTP secrets or otherwise cause the system to process the incoming PSKC data as trustworthy.
The problem only occurs when OATH Toolkit's libpskc is linked to libxmlsec older than 1.3.3, which was released on 2024-01-04. The release notes for libxmlsec says:
(xmlsec-core) Disabled KeyValue and DEREncodedKeyValue XML nodes by default. Use the '--enabled-key-data' option for the xmlsec command line utility or update the 'keyInfoCtx.enabledKeyData' parameter if you need to re-enable these nodes (also see question 3.5 in the FAQ).
The FAQ at https://www.aleksey.com/xmlsec/faq.html state that the old the behaviour is INSECURE AND NOT RECOMMENDED. Therefore we consider the underlying cause to be a libxmlsec security problem, which ought to have been patched and deployed earlier.
We encourage further research into other applications using libxmlsec that may be similarly affected. An earlier report from 2013 exists: https://www.aleksey.com/pipermail/xmlsec/2013/009717.html
Many distributions and deployments still uses libxmlsec 1.2.x including Debian 12 (1.2.37), Debian 13 (1.2.41), Ubuntu 22.04 (1.2.33), Ubuntu 24.04 (1.2.39) and RHEL 9 (1.2.33).
We are not aware of any active exploits in the wild of this flaw, and because of the outer protection required for PSKC data we do not know of, or can conceive, any realistic scenario where this problem could be exploited.
We acknowledge that this may be considered a problem in OATH Toolkit, and publish this advisory and a set of patches that prevents the problem from occurring when libpskc is used with older libxmlsec.
Severity: low. Likely no practical implications.
Affected versions and configurations ------------------------------------
Support for sign/verify of PSKC data was introduced in version 2.0.1 released on 2012-10-24 and not fixed up to and including 2.6.14 released on 2026-01-27. Version 2.6.15 released on 2026-10-13 prevents the problem.
Reproduce ---------
First create a baseline, with a sample PSKC file.
cat<<EOF>pskc.xml <?xml version="1.0" encoding="UTF-8"?> <KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc"> <KeyPackage> <DeviceInfo> <Manufacturer>Manufacturer</Manufacturer> <SerialNo>987654321</SerialNo> </DeviceInfo> <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp"> <AlgorithmParameters> <ResponseFormat Length="8" Encoding="DECIMAL"/> </AlgorithmParameters> <Data> <Secret> <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA= </PlainValue> </Secret> <Counter> <PlainValue>0</PlainValue> </Counter> </Data> </Key> </KeyPackage> </KeyContainer> EOF
Create a new trusted key and cert:
openssl req -x509 -newkey rsa:2048 -nodes -keyout trusted.key -out trusted.crt -days 3650 -subj "/CN=Trusted PSKC Signer"
Sign the PSKC data:
pskctool --sign --sign-key trusted.key --sign-crt trusted.crt pskc.xml > signed-legit.xml
Confirm verification works fine:
pskctool --verify --verify-crt trusted.crt signed-legit.xml OK
Now simulate an attacker that uses a XML template like this:
cat<<EOF>attack-template.xml <?xml version="1.0" encoding="UTF-8"?> <KeyContainer Version="1.0" Id="KC0001" xmlns="urn:ietf:params:xml:ns:keyprov:pskc"> <KeyPackage> <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp"> <Issuer>Attacker</Issuer> <Data> <Secret> <PlainValue>QUJDREVGR0gK</PlainValue> </Secret> </Data> </Key> </KeyPackage> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#";> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <Reference URI=""> <Transforms> <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <DigestValue/> </Reference> </SignedInfo> <SignatureValue/> <KeyInfo> <KeyValue/> </KeyInfo> </Signature> </KeyContainer> EOF
Create an attacker-controlled private key/cert and sign the PSKC data:
openssl req -x509 -newkey rsa:2048 -nodes -keyout attacker.key -out attacker.crt -days 3650 -subj "/CN=Attacker" xmlsec1 --sign --privkey-pem attacker.key --output attack-signed.xml attack-template.xml
Now witness that pskctool says it is properly signed:
pskctool --verify --verify-crt trusted.crt attack-signed.xml OK
If accepted by some processing entity, it would now trust the data and could rely on the OATH secret embedded in the file.
Recommendation --------------
We recommend you to review your outer PSKC data transport protection to confirm it provides authenticity, integrity and confidentiality of your data before it reaches libpskc/pskctool.
As an additional precaution (if the outer protection fails), we recommend you to upgrade to OATH Toolkit 2.6.15 (or later) and/or upgrade libxmlsec to 1.3.3 (or later). If upgrading is unpractical we recommended you to apply the relevant patches on the versions you use.
Solution --------
The patch we use is to restrict KeyValue to X509Data only:
https://codeberg.org/oath-toolkit/oath-toolkit/commit/5000be81a938a3bc01fb1a4636e89c19588ce7fb
History -------
Report by private e-mail from info <info () uc surf> on 2026-10-11. Correspondence about a proof-of-concept that lead to confirming the issue on 2026-10-12. CVE assigned by GitLab on 2026-10-12. Version 2.6.15 release on 2026-10-13.
Credits -------
Report by info <info () uc surf>, advisory and patch by Simon Josefsson.
OATH Toolkit pamoath usersfile ${HOME} privilege escalation --------------------------------------------------------------
Security Vulnerability ----------------------
OATH Toolkit provides two components liboath and pamoath. Pamoath is normally run as root and assumes that the usersfile path setting value points to a root-controlled and protected file containing the OATH secrets (cryptographic HMAC keys) for users.
The documentation suggests using a root-owned /etc/users.oath and to do chmod go-rw /etc/users.oath on it. The design assumes that file permissions are set up to prevent malicious read or writes to the credential file by unauthorized users. Whether file permissions are setup correctly or not is not checked by the code.
On every successful authentication, the file is rewritten to prevent OTP replay attacks. The rewriting logic works by acquiring a POSIX file lock on a newly created .lock file (in the same directory), and then writing file content to a newly created .new file (also in the same directory). File ownership of the new file is set to the same as the original file. The original usersfile is replaced atomically with the newly created file.
With the introduction of the ${HOME} indirection variable in the usersfile parameter the design assumptions no longer holds. The feature was added in version 2.6.7 released on 2021-05-01.
A typical setup when ${HOME} is used in a usersfile value such as usersfile=${HOME}/.config/oath.secrets is to allow users to manage their own credentials. The file is owned by the user who is responsible for adding the secret to it, and to set read/write permissions on the file appropriately.
The security problem is easy to exploit. To demonstrate the vulnerability with a vulnerable version and configuration, create a symbolic link $HOME/.config/oath.secrets.new that points to a privileged file such as /etc/shadow. After successful login as the user, pamoath/liboath has followed the symbolic link and rewrote the target file with new updated OATH credentials and sets ownership of that file to the user. The user is now able to modify /etc/shadow.
We are not aware of any active exploits in the wild of this flaw.
Affected versions and configurations ------------------------------------
OATH Toolkit pamoath and liboath version 2.6.7 to version 2.6.11 are affected. Version 2.6.12 prevents the attack.
The attack requires that the "usersfile" setting has a file or path component that is in a vulnerable location. The common setup with a write/read-protected usersfile=/etc/users.oath setup is not vulnerable.
While admin's may specify a "usersfile" in a world-writeable directory like /tmp we regard that as a configuration error. In most scenarios, only "usersfile" with ${HOME} in them should be regarded as a vulnerable configuration.
One vulnerable setting is usersfile=/home/joe/.config/system.oath giving joe the ability to modify root-owned files, assuming a non-root user joe with write access to anything below /home/joe. This is somewhat similar to having a system SSH configuration of HostKey /home/joe/sshhostkey which we believe is unlikely and also consider to be a configuration error.
Another example is via the ${USER} setting as in usersfile=/home/${USER}/.config/oath.secrets giving any non-root user the ability to control system files, assuming they have write access to anything below /home/${USER}. We have improved the documentation regarding ${USER} to be for those settings where root-controlled per-user files are desired. The intended use of ${USER} strings are for setups like usersfile=/var/oath/oath.${USER}.secrets where the files are per-user but owned by root and have file permissions setup to prevent read/write from the user. The root-ownership and file permission should prevent users from being able to reach their OATH credentials when ${USER} is used.
The pamoath fix only address configurations that uses ${HOME} and not any other vulnerable configurations. The liboath fix prevent direct attacks via .new and .lock symlinks, but other scenarios are possible and the input to oathauthenticateusersfile() MUST be a trusted filename -- suitable sanitization is application-dependent.
Details -------
The vulnerable code in liboath is inside oathauthenticateusersfile(), quoting code from version 2.6.11 which can be reviewed here:
https://gitlab.com/oath-toolkit/oath-toolkit/-/blob/oath-toolkit-2.6.11/liboath/usersfile.c?reftype=tags#L324
The lock file is acquired:
/ Open lockfile. / { int l; l = asprintf (&lockfile, "%s.lock", usersfile); if (lockfile == NULL || ((sizet) l) != strlen (usersfile) + 5) return OATHPRINTFERROR; lockfh = fopen (lockfile, "w"); if (!lockfh) { free (lockfile); return OATHFILECREATEERROR; } }
Since fopen("w") is used any existing file with the predictable .lock filename will be opened, including if that file happens to be a symbolic link that points elsewhere. The code is run as root, so a symbolic link pointing to confidential files under /etc are happily opened. Note that since "w" is used, the file will be automatically truncated. So this allows non-root to truncate root-owned files.
Reading on to the .new file handling:
https://gitlab.com/oath-toolkit/oath-toolkit/-/blob/oath-toolkit-2.6.11/liboath/usersfile.c?reftype=tags#L360
outfh = fopen (newfilename, "w"); if (!outfh) { free (newfilename); fclose (lockfh); free (lockfile); return OATHFILECREATEERROR; }
Similarily, this will open a file for writing, and later code will essentially copy data from the existing file into the new one. This new file is moved back into the original place.
The PAM module invokes oathauthenticateusersfile() in pamoath.c, which can be shown here:
https://gitlab.com/oath-toolkit/oath-toolkit/-/blob/oath-toolkit-2.6.11/pamoath/pamoath.c?reftype=tags#L452
The code is running as root so it may work on an untrustworthy filename. The design used to be that admin's specify a trusted pathname here, but the ${HOME} use-case broke this design assumption and the pathnames should then no longer be considered trusted.
Solution --------
Version 2.6.12 contains the following liboath patch to use fopen(wx):
https://gitlab.com/oath-toolkit/oath-toolkit/-/commit/3235a52f6b87cd1c5da6508f421ac261f5e33a70
Some non-glibc and non-ISO C11 platforms needs the following patch to enable gnulib's fopen(wx) workaround:
https://gitlab.com/oath-toolkit/oath-toolkit/-/commit/3271139989fde35ab0163b558fc29e80c3a280e5
Then pamoath.c is modified to call seteuid()/setegid() as follows:
https://gitlab.com/jas/oath-toolkit/-/commit/95ef255e6a401949ce3f67609bf8aac2029db418
A patch that applies cleanly to version 2.6.7 found in Debian 12.x bookworm is available here:
https://salsa.debian.org/debian/oath-toolkit/-/blob/debian/bookworm-security/debian/patches/pamoath-seteuid.patch
We recommend you to upgrade to version 2.6.12.
If that is unpractical we recommended you to apply the patches on top of your earlier version.
Reproducer ----------
Included in the 2.6.12 release is a C program to test if a liboath is vulnerable or not. It can be built as follows on a system with liboath properly installed:
git clone https://gitlab.com/oath-toolkit/oath-toolkit.git cd oath-toolkit git checkout oath-toolkit-2.6.12 cd liboath/tests cc -o tstfopen-wx tstfopen-wx.c $(pkg-config --libs --cflags liboath) rm -f cve.oath cve.oath.new cve.sshd-config cve.oath.lock printf 'HOTP/E/8\tsilver\t4711\t3132333435363738393031323334353637383930313233343536373839303132\n' > cve.oath echo my-magic-cookie > cve.sshd-config ln -s cve.sshd-config cve.oath.new ./tstfopen-wx cve.oath silver 670691 4711
When invoked on a Trisquel 11 system with liboath0 and liboath-dev version 2.6.7-3build1 installed, it will print the following:
Liboath fopen(wx) bug test for oath.h 2.6.7 liboath.so 2.6.7 FAIL: Liboath VULNERABLE to fopen(wx) bug.
To test a particular liboath use LDPRELOAD as follows:
LDPRELOAD=/usr/local/lib/x8664-linux-gnu/liboath.so.0 ./tstfopen-wx cve.oath silver 670691 4711
The liboath/tests/tstfopen-wx.sh script can be used to setup and invoke the C program testing two different vulnerable configurations.
Related work ------------
The logic for the usersfile handling was inspired by earlier versions of mod-authn-otp and pamgoogleauthenticator although their modern design appears to be somewhat different from pamoath's current code.
https://github.com/archiecobbs/mod-authn-otp
https://github.com/google/google-authenticator-libpam/
While the liboath oathauthenticateusersfile() vulnerability is not a typical "time-of-check, time-of-use" race condition (since there is no check happening in the code) it exhibits the same pattern in the code since fopen(w) is used instead of fopen(wx). Running GitLab semantic analysis (SAST) on the vulnerable code flagged it as problematic:
https://gitlab.com/oath-toolkit/oath-toolkit/-/security/vulnerabilities/139535897
https://gitlab.com/oath-toolkit/oath-toolkit/-/security/vulnerabilities/139535890
https://wiki.sei.cmu.edu/confluence/display/c/FIO45-C.+Avoid+TOCTOU+race+conditions+while+accessing+files
We have enabled GitLab SAST and Coverity scanning of OATH Toolkit and will review the findings.
SUSE's alternative patch and advisory can be found via:
https://security.opensuse.org/2024/10/04/oath-toolkit-vulnerability.html
It rely on Linux kernel specific features and uses fork() which was determined to be contrary to the liboath design, which aims to be portable to macOS and BSD and beyond. This alternative patch may be used by some vendors, and it is assumed to fix the security problem.
History -------
Fabian Vogt reported this issue in private e-mail on 2024-08-08. Matthias Gerstner reported the issue as a GitLab confidential issue on 2024-08-20. These reports came in during vacation time and were not read by the maintainer. Salvatore Bonaccorso reached out on 2024-09-29 via SMS, and the progress since then has been tracked in the bug tracker:
https://gitlab.com/oath-toolkit/oath-toolkit/-/issues/43
Credits -------
The problem was discovered by Fabian Vogt of SUSE. An initial patch against liboath was developed by Matthias Gerstner of SUSE Security Team. An alternative and portable patch to liboath and pamoath were developed by Simon Josefsson.